package dancrawler.server;

import dancrawler.common.Misc;
import dancrawler.client.gamecmd.ClientCmdRegister;
import dancrawler.proto.cmd.CmdHandlerRegister;
import dancrawler.server.Connection;
import dancrawler.server.ProtocolParser;
import dancrawler.server.events.DisconnectEvent;
import dancrawler.server.events.NewClientEvent;
import dancrawler.server.gamecmd.ServerCmdRegister;
import dancrawler.server.netMsg;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Hashtable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 */
public class Server {

    private class ServerAcceptThread extends Thread {

        Server cl;
        ServerSocket so;
        boolean stop;

        @Override
        public void run() {
            try {
                so = new ServerSocket(1881, 10, null);
                while (!stop) {
                    Socket ns = so.accept();
                    //ns.setSoTimeout(7000);

                    //ns.setPerformancePreferences(0, 10, 0);
                    ns.setTcpNoDelay(true);
                    ServerReadThread nrt = new ServerReadThread();
                    nrt.setDaemon(true);
                    ServerWriteThread nwt = new ServerWriteThread();
                    nwt.setDaemon(true);
                    nrt.cl = cl;
                    nwt.cl = cl;
                    Connection con = new Connection();
                    nrt.con = con;
                    nwt.con = con;
                    long t = System.currentTimeMillis() / 1000L;
                    con.setId(Long.toString(t));

                    con.setSocket(ns);
                    nrt.wt = nwt;
                    nwt.rt = nrt;
                    nrt.start();
                    nwt.start();
                    cl.gh.getEventDirector().postEvent(new NewClientEvent(con));
                    clients.put(con.getId(), con);
                }
            } catch (IOException ex) {
            }
            if (so != null) {
                try {
                    so.close();
                } catch (IOException ex) {
                }
            }
        }
    }

    private class ServerReadThread extends Thread {

        Server cl;
        ServerWriteThread wt;
        Connection con;

        @Override
        public void run() {
            ProtocolParser parser = new ProtocolParser();
            try {
                InputStream ins = con.getSocket().getInputStream();
                byte[] idata = new byte[1024];
                while (true) {
                    int n = ins.read(idata);
                    if (n == -1) {
                        throw new IOException();
                    }
                    parser.processData(idata, con, n);
                    netMsg msg = null;
                    while ((msg = con.popMsg()) != null) {

                        cl.inQueue.offer(msg);

                    }
                }
            } catch (IOException ex) {

                Socket s = con.getSocket();
                try {
                    clients.remove(con.getId());
                    s.close();
                } catch (IOException ex1) {
                }
                wt.interrupt();
                cl.gh.getEventDirector().postEvent(new DisconnectEvent(con.getId()));
            }
        }
    }

    private class ServerWriteThread extends Thread {

        Server cl;
        Connection con;
        ServerReadThread rt;

        @Override
        public void run() {
            OutputStream ons = null;
            try {
                ProtocolParser parser = new ProtocolParser();
                ons = con.getSocket().getOutputStream();
                byte[] outb = new byte[1024];
                ByteBuffer obuff = ByteBuffer.allocate(1024);
                while (true) {
                    netMsg msg = con.getOutQueue().take();
                    obuff.clear();
                    parser.writeMsg(obuff, msg);
                    obuff.flip();
                    obuff.get(outb, 0, obuff.limit());
                    ons.write(outb, 0, obuff.limit());

                }
            } catch (InterruptedException | IOException ex) {
            } finally {
                try {
                    clients.remove(con.getId());
                    if (ons != null) {
                        ons.close();
                    }
                    if (con.getSocket() != null) {
                        con.getSocket().close();
                    }

                } catch (IOException ex1) {
                }


                rt.interrupt();
            }
        }
    }
    private Connection serverCon = new Connection();
    private ServerReadThread crt = new ServerReadThread();
    private ServerWriteThread cwt = new ServerWriteThread();
    private InetSocketAddress addr;
    private GameObjectRegister reg = new GameObjectRegister();
    private CmdHandlerRegister hreg;// = new CmdHandlerRegister();
    private BlockingQueue<netMsg> inQueue = new LinkedBlockingQueue<>();
    private ServerAcceptThread act;
    private GameHandler gh;
    private Hashtable<String, Connection> clients = new Hashtable<>();

    public Server(CmdHandlerRegister reg) {
        hreg = reg;

    }

    public void changeClientName(String old, String newn) {
        clients.get(old).setId(newn);
        clients.remove(old);
        clients.put(newn, serverCon);
    }

    public void setGameHandler(GameHandler gh) {
        this.gh = gh;
    }

    public Connection getClientConnection(String id) {
        return clients.get(id);
    }

    public Collection<Connection> getConnections() {
        return clients.values();
    }

    public BlockingQueue<netMsg> getInQueue() {
        return inQueue;
    }

    public void startServer() throws UnknownHostException, IOException {

        //socket = new Socket(host, port);
        act = new ServerAcceptThread();
        act.cl = this;
        act.start();

    }

    public void stopServer() {
        try {
            if (act.so != null) {
                act.so.close();
            }
        } catch (IOException ex) {
        }
        for (Connection c : clients.values()) {
            try {
                if (c.getSocket() != null) {
                    c.getSocket().close();
                }

            } catch (IOException ex) {
            }
        }
        clients.clear();
    }

    public GameObjectRegister getObjectRegister() {
        return reg;
    }
}
